package com.spzx.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.product.domain.ProductSku;
import com.spzx.product.mapper.ProductSkuMapper;
import com.spzx.product.query.SkuQuery;
import com.spzx.product.service.IProductSkuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.product.api.domain.ProductSkuVo;
import com.spzx.product.api.domain.SkuPriceVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 商品sku 服务实现类
 * </p>
 *
 * @author atguigu
 * @since 2024-12-20
 */
@Slf4j
@Service
public class ProductSkuServiceImpl extends ServiceImpl<ProductSkuMapper, ProductSku> implements IProductSkuService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public List<ProductSkuVo> getTopSale() {
        return baseMapper.getTopSale();
    }

    @Override
    public List<ProductSkuVo> selectProductSkuList(SkuQuery skuQuery) {
        return baseMapper.selectProductSkuList(skuQuery);
    }

    @Override
    public SkuPriceVo getSkuPrice(Long skuId) {
        SkuPriceVo skuPriceVo = new SkuPriceVo();
        LambdaQueryWrapper<ProductSku> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProductSku::getId,skuId)
                .select(ProductSku::getSalePrice,ProductSku::getMarketPrice);
        ProductSku productSku = baseMapper.selectOne(queryWrapper);
        skuPriceVo.setSkuId(skuId);
        skuPriceVo.setMarketPrice(productSku.getMarketPrice());
        skuPriceVo.setSalePrice(productSku.getSalePrice());
        return skuPriceVo;
    }


    @Override
    public Map<String, Long> getSkuSpecValueMap(Long productId) {
        HashMap<String, Long> map = new HashMap<>();
        //根据商品id，寻找对应的商品规格sku信息
        LambdaQueryWrapper<ProductSku> queryWrapper = new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getProductId, productId)
                .select(ProductSku::getSkuSpec, ProductSku::getId);
        List<ProductSku> productSkuList = baseMapper.selectList(queryWrapper);
        //对productSkuList进行操作，遍历它，从中取值并添加到map中
        for (ProductSku productSku : productSkuList) {
            map.put(productSku.getSkuSpec(),productSku.getId());
        }
        return map;
    }

    @Override
    public ProductSku getProductSku(Long skuId) {
        String bitKey = "product:sku:data";
        //进来第一步，为了防止缓存穿透，先去Bitmap中查找用户发来请求中的skuId是否是存在的真实数据
        Boolean isExist = redisTemplate.opsForValue().getBit(bitKey, skuId);
        //若不存在，直接给这恶意用户这个鸟人抛出个自定义异常，让全局异常处理器去处理
        if (!isExist){
            //若不存在，那么就抛出自定义异常，让全局异常处理器捕获帮我们自动返回结果
            throw new ServiceException("商品不存在！");
        }
        //skuId存在，那么就正常执行下面的逻辑
        try {
            /**
             * 加分布式锁
             */
            //先去获取该锁的Key
            String lockKeyName = "product:sku:lock:" + skuId;
            //获取锁的值，锁的值必须是唯一的
            String lockValue = UUID.randomUUID().toString().replaceAll("-","");
            //尝试获取锁，抢锁，抢！
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKeyName,lockValue,5, TimeUnit.SECONDS);
            //若获取到了锁
            if (flag){
                try {
                    //先去缓存中(redis)查询数据
                    //设置缓存商品的键名
                    String ProductKeyName = "product:sku:" + skuId;
                    //根据键名获取商品的值
                    ProductSku productCache = (ProductSku) redisTemplate.opsForValue().get(ProductKeyName);
                    //判断获取到的缓存数据是否为空,如果不为空，说明有数据，那么就直接将缓存返回
                    if (productCache != null){
                        log.info("查询的商品命中缓存！返回！");
                        return productCache;
                    }
                    //未命中缓存，直接去数据库中查询
                    ProductSku productSku = baseMapper.selectById(skuId);
                    //获取数据，存到缓存里,并且设置过期时间
                    long ttl = 600;
                    //在这里做个判断，我们要解决缓存穿透的问题
                    /**
                     * 缓存穿透：用户传来的请求数据在缓存以及数据库中均不存在
                     * 那么我们就需要将这个查询到的空结果缓存到数据中，并且设置一个较短的过期时间来节省内存
                     * 但是！我们不能直接给当前的结果直接缓存一个null值，因为前面会判断缓存是否为null
                     * 若缓存为null，则直接去查询数据库，因此缓存失效，所以我们不能缓存一个null值
                     * 我们应该缓存一个空对象，一个空的ProductSku对象，即new ProductSku()
                     */
                    /*
                    因为我们使用Bitmap解决了缓存穿透的问题，因此不需要再做一次判断，因为前面已经判断过了，
                    if (productSku == null){
                        productSku = new ProductSku();
                    }
                    */
                    redisTemplate.opsForValue().set(ProductKeyName,productSku,ttl,TimeUnit.SECONDS);
                    //将从数据库中查询到的结果返回
                    return productSku;
                }finally {
                    //释放锁
                    //使用LUA脚本来同时进行判断是否同一把锁，并且释放锁
                    String script = """
                        if redis.call('get',KEYS[1]) == ARGV[1] then
                            return redis.call('delete',KEYS[1])
                        else
                            return 0
                        end
                        """;
                    Boolean lock = (Boolean) redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class),
                            Arrays.asList(lockKeyName),
                            lockValue);
                    log.info("释放锁的结果为：" + lock);
                }
            }else {
                //如果没有获取到锁，那么就尝试自旋重试获取锁
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log.info("获取锁失败，自旋：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
                return this.getProductSku(skuId);
            }
        } catch (Exception e) {
            //兜底方案：如果Redis服务器宕机了，那么就直接去数据库中查询
            log.error("[商品服务]查询商品详情内部发生异常:{}",e.getMessage());
            //如果业务中出现了异常，那么直接去数据库中查询结果并返回
            return baseMapper.selectById(skuId);
        }
    }
    /**
     * 根据skuId集合，批量获取多个商品的最新价格信息
     * @param skuIdList
     * @return
     */
    @Override
    public List<SkuPriceVo> getSkuPriceList(List<Long> skuIdList) {
        List<ProductSku> productSkuList = baseMapper.selectList(new LambdaQueryWrapper<ProductSku>()
                .in(ProductSku::getId, skuIdList)
                .select(ProductSku::getId, ProductSku::getSalePrice));
        return productSkuList.stream().map(productSku -> {
            SkuPriceVo skuPriceVo = new SkuPriceVo();
            skuPriceVo.setSkuId(productSku.getId());
            skuPriceVo.setSalePrice(productSku.getSalePrice());
            return skuPriceVo;
        }).toList();
    }
}
